import * as THREE from "three";
import * as TWEEN from "@tweenjs/tween.js";
import {
  CSS2DRenderer,
  CSS2DObject,
} from "three/addons/renderers/CSS2DRenderer.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { KujialeParamModel } from "@/core3d/types/KujialeJsonModels/KujialeParamModel";
import {
  AssemblyProductGroup,
  Product3DGroup,
} from "./types/DrawingGroups/groups";
import { AssemblyProductModel, ProductModel } from "./types/Models";
import {
  DrawProductModelOnAbsPosition,
  DrawAssemblyProduct,
  ChangeAssemblyProductPartVisibleByTextureName,
  ChangeAssemblyProductPartVisibleByCategory,
  ChangeProductPartVisibleByTexture,
  ChangeProductPartVisibleByCategory,
  test_loft,
  offsetToCenter,
  reset_grids
} from "./drawing";
import { BaseMode } from "./modes/BaseMode";
import { InitMode } from "./modes/InitMode";
import type { AnalysisResult } from "./types/AnlysisResult";
import type { KujialeAssemblyModel } from "./types/KujialeJsonModels/KujialeAssemblyModel";
import { GenerateTextureDict, GetTextureDictOnGroups } from "./drawing/texture";
import { labelRenderer } from "./tag";
import { useCurrentModelStore } from "@/stores/current";
import { useLibStore } from "@/stores/library";
import { Base3DGroup } from "./types/DrawingGroups/groups";
import { type LogItem } from "./types/Log/LogItem";

export const scene = new THREE.Scene();
scene.background = new THREE.Color("#ffffff");
export let renderer: THREE.WebGLRenderer;
export let camera: THREE.PerspectiveCamera;
export let mode: BaseMode;
export let controls: OrbitControls;
export let labelrenderer: CSS2DRenderer;
export let tweenGroup = new TWEEN.Group();
export let canvas: HTMLCanvasElement;

export function initThree() {
  canvas = document.querySelector("#three") as HTMLCanvasElement;
  renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth - 300 - 450, window.innerHeight - 100); //左右的宽度
  labelrenderer = labelRenderer(
    document.querySelector("#canvascontainer") as HTMLDivElement
  );

  camera = new THREE.PerspectiveCamera(
    45,
    canvas.offsetWidth / canvas.offsetHeight,
    1,
    40000
  );
  camera.position.set(3500, 3500, 3500);
  camera.lookAt(new THREE.Vector3(0, 0, 0));

  controls = new OrbitControls(camera, renderer.domElement);
  controls.enableDamping = false;

  const ambientLight = new THREE.AmbientLight("rgb(255,255,255)", 0.8);
  scene.add(ambientLight);

  // const gui = new GUI();
  // gui.domElement.style.right = '0px';
  // gui.domElement.style.width = '200px';
  // //创建一个对象，对象属性的值可以被GUI库创建的交互界面改变
  // const obj = {
  //     x: 30,
  // };
  // // gui增加交互界面，用来改变obj对应属性
  // gui.add(obj, 'x', 0, 100);

  // //创建stats对象
  // const stats = new Stats();
  // //stats.domElement:web页面上输出计算结果,一个div元素，
  // document.body.appendChild(stats.dom);

  const axeshelper = new THREE.AxesHelper(100000);
  scene.add(axeshelper);

  const gridhelper = new THREE.GridHelper(20000, 50);
  scene.add(gridhelper);

  //test_loft(scene);
  const lib = useLibStore();
  lib.load_from_storage();

  function animate() {
    //stats.update();
    controls.update();
    renderer.render(scene, camera);
    labelrenderer.render(scene, camera);
    tweenGroup.update();
    requestAnimationFrame(animate);
  }
  animate();

  window.addEventListener("resize", () => {
    //自适应屏幕大小https://blog.csdn.net/weixin_45966674/article/details/135160043
    renderer.setSize(window.innerWidth - 240, window.innerHeight);
    camera.aspect = (window.innerWidth - 240) / window.innerHeight;
    camera.updateProjectionMatrix();
  });

  const store = useCurrentModelStore();
  //store.setThreeEnv(canvas, scene, camera,tweenGroup);
  store.setThreeEnv(tweenGroup);
  return { scene, canvas, camera, tweenGroup };
}

export const clearScene = (...objects: THREE.Object3D[]) => {
  //scene.clear();
  var productgroups = scene.children.filter((it) => it instanceof Base3DGroup);
  scene.remove(...productgroups);

  const traverse = (g: THREE.Group) => {
    g.children.forEach((it) => {
      if (it instanceof Base3DGroup) {
        traverse(it);
      } else if (it instanceof THREE.Mesh) {
        it.material.dispose();
        it.geometry.dispose();
      }
    });
  };

  productgroups.forEach((it) => {
    traverse(it);
  });
};

export const drawAllModels = (
  models: KujialeParamModel[],
  KujialeAssemblyModels: KujialeAssemblyModel[]
): AnalysisResult => {
  clearScene(); //删除canvas上原有的内容

  const lib = useLibStore(); //获取绘制所需要的环境，包括颜色/截面数据
  const colorDict: { [key: string]: string } = lib.colors;
  const profiles = lib.profiles;
  GenerateTextureDict(models, colorDict);
  const logs: LogItem[] = [];
  const context = { profiles, colors: colorDict, logs };

  //先绘制组合产品
  KujialeAssemblyModels.forEach((a) => {
    var product = new AssemblyProductModel(
      models.filter(
        (it) => a.paramModelIds.find((p) => p == it.id) != undefined
      ),
      a
    );
    const amgroup = new AssemblyProductGroup(product);
    DrawAssemblyProduct(amgroup, context);
    scene.add(amgroup);
  });
  //再绘制没有组合的产品
  var restmodels = models.filter((m) => {
    if (KujialeAssemblyModels) {
      if (KujialeAssemblyModels.length == 0) {
        return true;
      } else {
        return KujialeAssemblyModels.every(
          (am) => !am.paramModelIds.some((amid) => amid == m.id)
        );
      }
    }
    return true;
  });
  restmodels.forEach((m) => {
    const group = new Product3DGroup(new ProductModel(m));
    DrawProductModelOnAbsPosition(group, context);
    scene.add(group);
  });

  //偏移
  offsetToCenter(scene);
  reset_grids(scene);

  mode = new InitMode(renderer.domElement, camera, scene);
  changeMode(mode, false);


  return {
    code: context.logs.filter((l) => l.level > 2).length > 0 ? -1 : 0,//-1表示error,0表示没问题
    colors: context.colors,
    logs: context.logs,
  };
};

export const DisplayKujialeAssemblyModelAndInvisibleOthers = (
  kujialeAssemblyModel: KujialeAssemblyModel
): AnalysisResult => {
  var groups = scene.children.filter(
    (g) =>
      g instanceof AssemblyProductGroup &&
      g.model instanceof AssemblyProductModel &&
      g.model.kujialeAssemblyModel == kujialeAssemblyModel
  ) as Base3DGroup[];
  const colorDict: { [key: string]: string } = GetTextureDictOnGroups(groups);

  let group: THREE.Group = new THREE.Group();
  scene.children
    .filter((it) => it instanceof Base3DGroup)
    .forEach((g) => {
      if (g instanceof AssemblyProductGroup) {
        if (
          (g.model as AssemblyProductModel).kujialeAssemblyModel ==
          kujialeAssemblyModel
        ) {
          g.visible = true;
          group = g;
          if (
            g.children &&
            g.children.length > 0 &&
            g.children[0] instanceof THREE.Mesh
          ) {
            g.children[0].visible = false;
          }
        } else {
          g.visible = false;
        }
      } else {
        g.visible = false;
      }
    });

  return {
    code: 0,
    colors: colorDict,
    effectGroup: group,
    logs: [],
  };
};

//两个方法用于保存bind方法的结果，否则无法removelistner
var mousedownFn: (ev: MouseEvent) => void;
var mousemoveFn: (ev: MouseEvent) => void;
var contextmenuFn: (ev: MouseEvent) => void;

export function changeMode(newMode: BaseMode, isStepIn: boolean) {
  if (mode) {
    renderer.domElement.removeEventListener("click", mousedownFn, false);
    renderer.domElement.removeEventListener("mousemove", mousemoveFn, false);
    renderer.domElement.removeEventListener(
      "contextmenu",
      contextmenuFn,
      false
    );
    if (isStepIn) {
      mode.onStepInMode();
    } else {
      mode.onExitMode();
    }
  }

  mousedownFn = newMode.mousedown.bind(newMode);
  mousemoveFn = newMode.mousemove.bind(newMode);
  contextmenuFn = newMode.contextmenu.bind(newMode);
  mode = newMode;
  mode.init();
  renderer.domElement.addEventListener("mousemove", mousemoveFn, false); //addEventListener中的this默认是事件发生的dom元素
  renderer.domElement.addEventListener("click", mousedownFn, false);
  renderer.domElement.addEventListener("contextmenu", contextmenuFn, false);
}

export function changePartVisibleByTexture(colors: { [key: string]: boolean }) {
  scene.children.forEach((g) => {
    if (g instanceof Product3DGroup) {
      ChangeProductPartVisibleByTexture(g, colors);
    } else if (g instanceof AssemblyProductGroup) {
      ChangeAssemblyProductPartVisibleByTextureName(g, colors);
    }
  });
}

export function changeSubVisibleByCategory(categories: {
  [key: number]: boolean;
}) {
  scene.children.forEach((g) => {
    if (g instanceof Product3DGroup) {
      ChangeProductPartVisibleByCategory(g, categories);
    } else if (g instanceof AssemblyProductGroup) {
      ChangeAssemblyProductPartVisibleByCategory(g, categories);
    }
  });
}

export function getParsedJsonModelsFromRaw(json: any): KujialeParamModel[] {
  var newmodels = json.paramModel.map((it: KujialeParamModel) => {
    return getParsedParamModelFromRaw(it);
  });
  return newmodels;
}

const getParsedParamModelFromRaw = (
  rawmodel: KujialeParamModel
): KujialeParamModel => {
  const newmodel = new KujialeParamModel();
  Object.assign(newmodel, rawmodel);
  if (newmodel.subModels) {
    let newsubmodels: KujialeParamModel[] = [];
    newmodel.subModels.forEach((child) => {
      let newchild = getParsedParamModelFromRaw(child);
      newsubmodels.push(newchild);
    });
    newmodel.subModels = newsubmodels;
  }
  return newmodel;
};

export const fitCameraToObject = function (object: THREE.Object3D) {
  const offset = 1.24;

  const boundingBox = new THREE.Box3();
  boundingBox.setFromObject(object);

  var middle = new THREE.Vector3();
  var size = new THREE.Vector3();
  boundingBox.getSize(size);

  // figure out how to fit the box in the view:
  // 1. figure out horizontal FOV (on non-1.0 aspects)
  // 2. figure out distance from the object in X and Y planes
  // 3. select the max distance (to fit both sides in)
  //
  // The reason is as follows:
  //
  // Imagine a bounding box (BB) is centered at (0,0,0).
  // Camera has vertical FOV (camera.fov) and horizontal FOV
  // (camera.fov scaled by aspect, see fovh below)
  //
  // Therefore if you want to put the entire object into the field of view,
  // you have to compute the distance as: z/2 (half of Z size of the BB
  // protruding towards us) plus for both X and Y size of BB you have to
  // figure out the distance created by the appropriate FOV.
  //
  // The FOV is always a triangle:
  //
  //  (size/2)
  // +--------+
  // |       /
  // |      /
  // |     /
  // | F° /
  // |   /
  // |  /
  // | /
  // |/
  //
  // F° is half of respective FOV, so to compute the distance (the length
  // of the straight line) one has to: `size/2 / Math.tan(F)`.
  //
  // FTR, from https://threejs.org/docs/#api/en/cameras/PerspectiveCamera
  // the camera.fov is the vertical FOV.

  const fov = camera.fov * (Math.PI / 180);
  const fovh = 2 * Math.atan(Math.tan(fov / 2) * camera.aspect);
  let dx = size.z / 2 + Math.abs(size.x / 2 / Math.tan(fovh / 2));
  let dy = size.z / 2 + Math.abs(size.y / 2 / Math.tan(fov / 2));
  let cameraZ = Math.max(dx, dy);

  // offset the camera, if desired (to avoid filling the whole canvas)
  if (offset !== undefined && offset > 0) cameraZ *= offset;

  camera.position.set(0, 0, cameraZ);

  // set the far plane of the camera so that it easily encompasses the whole object
  const minZ = boundingBox.min.z;
  const cameraToFarEdge = minZ < 0 ? -minZ + cameraZ : cameraZ - minZ;

  camera.far = cameraToFarEdge * 3;
  camera.updateProjectionMatrix();

  if (controls !== undefined) {
    // set camera to rotate around the center
    controls.target = new THREE.Vector3(0, 0, 0);

    // prevent camera from zooming out far enough to create far plane cutoff
    controls.maxDistance = cameraToFarEdge * 2;
  }
};
